package org.xx.armory.spring5.test.mvc;

import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.xx.armory.commons.MapUtils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.StreamSupport.stream;
import static org.xx.armory.spring5.mvc.ModelUtils.SINGLE_BEAN_NAME;

public final class MvcResultUtils {
    private static final MediaType JSON_UTF8 = new MediaType("application", "json", UTF_8);

    private MvcResultUtils() {
        throw new AssertionError();
    }

    public static ResultMatcher contentIsJson() {
        return MockMvcResultMatchers.content().contentType(JSON_UTF8);
    }

    private static Map<String, Object> getModel(
            MvcResult result
    ) {
        final var modelAndView = result.getModelAndView();
        return modelAndView != null ? modelAndView.getModel() : Collections.emptyMap();
    }

    public static String getModelStringAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getString(getModel(result), key);
    }

    public static Integer getModelIntegerAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getInteger(getModel(result), key);
    }

    public static Long getModelLongAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getLong(getModel(result), key);
    }

    public static BigDecimal getModelDecimalAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getDecimal(getModel(result), key);
    }

    public static Boolean getModelBooleanAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getBoolean(getModel(result), key);
    }

    public static Date getModelDateAttribute(
            MvcResult result,
            String key
    ) {
        return MapUtils.getDate(getModel(result), key);
    }

    public static <T> T getModelSingleBeanAttribute(
            MvcResult result,
            Class<T> clazz
    ) {
        final var modelAndView = result.getModelAndView();
        if (modelAndView == null) {
            return null;
        }

        return clazz.cast(modelAndView.getModel().get(SINGLE_BEAN_NAME));
    }

    public static <E> List<E> getModelSingleListAttribute(
            MvcResult result,
            Class<E> clazz
    ) {
        final var modelAndView = result.getModelAndView();

        if (modelAndView == null) {
            return null;
        }

        final var bean = modelAndView.getModel().get(SINGLE_BEAN_NAME);
        if (bean == null) {
            return null;
        }

        if (bean instanceof Iterable) {
            return stream(((Iterable<?>) bean).spliterator(), false)
                    .map(clazz::cast).collect(Collectors.toUnmodifiableList());
        } else {
            throw new ClassCastException("Cannot cast " + bean + " to " + List.class);
        }
    }

    public static <T> T getModelAttribute(
            MvcResult result,
            String key,
            Class<T> clazz
    ) {
        final var obj = getModel(result).getOrDefault(key, null);
        if (obj == null) {
            return null;
        } else {
            return clazz.cast(obj);
        }
    }
}
